package gwtgaetools.server;

import gwtgaetools.shared.model.GenericMessage;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;


import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.server.rpc.RPC;
import com.google.gwt.user.server.rpc.SerializationPolicy;
import com.google.gwt.user.server.rpc.SerializationPolicyLoader;

/**
 * Wraps the ChannelService up in our application-specific
 * push-messaging infrastructure.
 *
 * @author Toby Reyelts
 */
public class PushServer<M extends GenericMessage<?>> {

  private static final Method dummyMethod = getDummyMethod();

  private static SerializationPolicy serializationPolicy;

  public PushServer(String appPath) {
	  createPushSerializationPolicy(appPath);
  }
  

/**
   * Creates a new SerializationPolicy for push RPC.
   */
  public void createPushSerializationPolicy(String appPath) {
    // We're reading all of the SerializationPolicy files in the app
    // and merging them together. This approach seems a bit crappy,
    // but less crappy than the other alternatives.

    File[] files = new File(appPath).listFiles(new FilenameFilter() {
      public boolean accept(File dir, String name) {
        return name.endsWith(".gwt.rpc");
      }
    });

    List<SerializationPolicy> policies = new ArrayList<SerializationPolicy>();

    for (File f : files) {
      try {
        BufferedInputStream input = new BufferedInputStream(
            new FileInputStream(f));
        policies.add(SerializationPolicyLoader.loadFromStream(input, null));
      } catch (Exception e) {
        throw new RuntimeException(
            "Unable to load a policy file: " + f.getAbsolutePath());
      }
    }

    serializationPolicy = new MergedSerializationPolicy(policies);
  }

//  public String encodeMessage(ArrayList<M> msg) {
//	    try {
//	      return RPC.encodeResponseForSuccess(dummyMethod, msg, serializationPolicy);
//	    } catch (SerializationException e) {
//	      throw new RuntimeException("Unable to encode a message for push.\n" + msg, e);
//	    }
//	  }
//
  public String encodeMessage(M msg) {
    try {
      return RPC.encodeResponseForSuccess(dummyMethod, msg, serializationPolicy);
    } catch (SerializationException e) {
      throw new RuntimeException("Unable to encode a message for push.\n" + msg, e);
    }
  }

  /**
   * This method exists to make GWT RPC happy.
   * <p>
   * {@link RPC#encodeResponseForSuccess(java.lang.reflect.Method, Object)}
   * insists that we pass it a Method that has a return type equal to the
   * object we're encoding. What we really want to use is
   * {@link RPC#encodeResponse(Class, Object, boolean, int, com.google.gwt.user.server.rpc.SerializationPolicy)},
   * but it is unfortunately private.
   */
  @SuppressWarnings("unused")
private M dummyMethod() {
    throw new UnsupportedOperationException("This should never be called.");
  }

  private static Method getDummyMethod() throws RuntimeException {
    try {
      return PushServer.class.getDeclaredMethod("dummyMethod");
    } catch (NoSuchMethodException e) {
      throw new RuntimeException("Unable to find the dummy RPC method.");
    }
  }

  private static class MergedSerializationPolicy extends SerializationPolicy {
    List<SerializationPolicy> policies;

    MergedSerializationPolicy(List<SerializationPolicy> policies) {
      this.policies = policies;
    }

    @Override
    public boolean shouldDeserializeFields(Class<?> clazz) {
      for (SerializationPolicy p : policies) {
        if (p.shouldDeserializeFields(clazz)) {
          return true;
        }
      }
      return false;
    }

    @Override
    public boolean shouldSerializeFields(Class<?> clazz) {
      for (SerializationPolicy p : policies) {
        if (p.shouldSerializeFields(clazz)) {
          return true;
        }
      }
      return false;
    }

    @Override
    public void validateDeserialize(Class<?> clazz)
        throws SerializationException {
      SerializationException se = null;
      for (SerializationPolicy p : policies) {
        try {
          p.validateDeserialize(clazz);
          return;
        } catch (SerializationException e) {
          se = e;
        }
      }
      throw se;
    }

    @Override
    public void validateSerialize(Class<?> clazz) throws SerializationException {
      SerializationException se = null;
      for (SerializationPolicy p : policies) {
        try {
          p.validateSerialize(clazz);
          return;
        } catch (SerializationException e) {
          se = e;
        }
      }
      throw se;
    }
  }

}
